/// Framework Core Low-Level Generics Collection Process
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.collections;

{
  *****************************************************************************

   Generics Collections as used by all framework units
   - JSON-aware IList<> List Storage
   - JSON-aware IKeyValue<> Dictionary Storage
   - Collections Factory for IList<> and IKeyValue<> Instances

    In respect to generics.collections, this unit uses interfaces as variable
   holders, and leverage them to reduce the generated code as much as possible,
   as the Spring4D 2.0 framework does, but for both Delphi and FPC.
    It also publishes TDynArray and TSynDictionary high-level features like
   JSON/binary serialization or thread safety with Generics strong typing.

  *****************************************************************************
}

interface

{$I ..\mormot.defines.inc}

{$ifdef HASGENERICS} // do-nothing unit on oldest compilers (e.g. < Delphi 2010)

// FPC 3.2+ and Delphi XE8+ allow to gather most common specializations in this
// unit and not in the end-user units to reduce executable code size
// - NOSPECIALIZE disable ahead-of-time compilation and make naive bloated generics
// - you may try this conditional to circumvent some Delphi internal errors
// - see also SPECIALIZE_HASH, SPECIALIZE_SMALL SPECIALIZE_WSTRING conditionals
{.$define NOSPECIALIZE}


uses
  classes,
  contnrs,
  sysutils,
  mormot.core.base,
  mormot.core.os,
  mormot.core.unicode,
  mormot.core.text,
  mormot.core.buffers,
  mormot.core.data,
  mormot.core.rtti,
  mormot.core.json;


// note: defined "var value"  instead of "out value" to avoid finalizer calls


{ ************** JSON-aware IList<> List Storage }

// Delphi allows SizeOf(T) in TSynEnumerator<T>.MoveNext but FPC doesn't
{$ifdef FPC}
  {$define NOSIZEOFT}
{$endif FPC}

type
  TSynListAbstract = class;

  /// abstract execution context for the TSynEnumerator<T> record
  TSynEnumeratorState = record
    {$ifdef NOSIZEOFT} ItemSize, {$endif}
    Current, After: PtrUInt; // 2-3 pointers on stack
  end;

  /// efficient mean to iterate over a generic collection of a specific type
  // - we redefined our own record type for better performance: it properly
  // inlines, and allocates as 2-3 pointers on stack with no try..finally
  TSynEnumerator<T> = record
  private
    fState: TSynEnumeratorState;
    type
      PT = ^T;
    // some property accessor
    function DoGetCurrent: T; inline;
  public
    /// this property is needed for any enumerator
    property Current: T
      read DoGetCurrent;
    /// go to the next item iterated in this collection
    function MoveNext: boolean; inline;
    /// self-reference is needed for IList<T>.Range custom enumerator
    function GetEnumerator: TSynEnumerator<T>; inline;
  end;

  /// exception class raised by IList<T>
  ESynList = class(ESynException);

  /// how Collections.NewList<T> will handle its IList<T> storage
  // - by default, string values would be searched following exact case,
  // unless the loCaseInsensitive option is set
  // - by default, managed values and T*ObjArray will delete their content
  // unless the loNoFinalize option is set (handle with care to avoid mem leaks)
  // - loCreateUniqueIndex will maintain a hash table over the items so that
  // Add() would avoid any duplicate and Find() perform in O(1) fast lookup -
  // note that aSortAs could be set in Collections.NewPlainList<> to index the
  // first field of a record instead of the whole collection item
  TListOptions = set of (
    loCaseInsensitive,
    loNoFinalize,
    loCreateUniqueIndex);

  /// customize IList<T>.Pop() behaviour
  // - popPeek won't remove the item from the list, just copy the value
  // - Add+Pop implement a LIFO (Last-In-First-Out) stack by default, but
  // FIFO (First-In-First-Out) if popFromHead is defined
  TListPop = set of (
    popPeek,
    popFromHead);

  /// gives access to a generics-based collection of items
  // - as generated by Collections.NewList<T> main factory
  // - defined as an interface for automatic memory management
  // - this instance is not thread-safe, but is implemented via a TDynArray
  // wrapper, optionally with a hash table for fast Find() lookup
  IList<T> = interface
    // some property accessors
    function GetItem(ndx: PtrInt): T;
    procedure SetItem(ndx: PtrInt; const value: T);
    function GetCount: PtrInt;
    procedure SetCount(value: PtrInt);
    function GetCapacity: PtrInt;
    procedure SetCapacity(value: PtrInt);
    function GetComparer: TDynArraySortCompare;
    procedure SetComparer(customcompare: TDynArraySortCompare);
    /// append a new value to the collection
    // - returns the index of the newly added item
    // - always append the new item at the end, unless loCreateUniqueIndex was
    // defined and then any duplicate is ignored and existing index is returned
    function Add(const value: T): PtrInt;
    /// insert a new value to the collection
    // - raise ESynList if loCreateUniqueIndex is set: use Remove() then Add()
    procedure Insert(ndx: PtrInt; const value: T);
    /// delete one item inside the collection from its index
    // - the deleted item is finalized if necessary
    // - raise ESynList if loCreateUniqueIndex is defined: use Remove()
    function Delete(ndx: PtrInt): boolean;
    /// delete one item inside the collection from its value
    // - the deleted item is finalized if necessary
    // - is the proper way of deleting an item if loCreateUniqueIndex is defined
    function Remove(const value: T): boolean;
    /// get and remove the last item stored in the collection
    // - set popPeek in opt if you don't want to remove the item, just copy its value
    // - Add+Pop implement a LIFO (Last-In-First-Out) stack by default
    // - Add+Pop implement a FIFO (First-In-First-Out) stack if popFromHead is
    // set - but slower, since all existing data is moved in memory by Pop()
    // - returns true if the item was successfully copied and removed
    function Pop(var dest: T; opt: TListPop = []): boolean;
    /// delete all stored items
    procedure Clear;
    /// will reverse all collection items, in place
    procedure Reverse;
    /// sort the collection items
    // - use the main Comparer function from RTTI, unless customcompare is set
    procedure Sort(customcompare: TDynArraySortCompare = nil); overload;
    /// sort a collection range
    // - use the main Comparer function from RTTI, unless customcompare is set
    // - this method allows to sort only some part of the items
    procedure Sort(start, stop: integer;
      customcompare: TDynArraySortCompare = nil); overload;
    /// sort the collection items using an external lookup array of indexes
    // - use the main Comparer function from RTTI, unless customcompare is set
    // - in comparison to the Sort method, this overload won't change the
    // collection content, but only create (or update) the supplied indexes[]
    // - if the indexes lookup table has less items than the collection,
    // its content will be recreated
    procedure Sort(var indexes: TIntegerDynArray;
      customcompare: TDynArraySortCompare = nil); overload;
    /// sort the collection, using a comparison property method (not function)
    // - you could optionally sort in reverse order
    procedure Sort(const customcompare: TOnDynArraySortCompare;
      descending: boolean = false); overload;
    /// search and add an item inside a sorted collection
    // - a sorted collection will use O(log(n)) binary search
    // - this method will use the main Comparer function for the search
    // - returns the index of the existing Item if wasadded^=false
    // - returns the sorted index of the inserted Item if wasadded^=true
    // - if the collection is not sorted, returns -1 and wasadded^=false
    // - raise ESynList if loCreateUniqueIndex is set: use plain Add()
    function AddSorted(const value: T; wasadded: PBoolean = nil): integer;
    /// is true if Sort() has just been called, or AddSorted() used
    function Sorted: boolean;
    /// default search for a value inside this collection
    // - brute force O(n) using RTTI default comparison (not Comparer) and the
    // loCaseInsensitive option
    // - is likely to be faster for simple ordinal types (bytes, integers)
    // - for a sorted array, or a custom comparison function, see Find()
    function IndexOf(const value: T): PtrInt;
    /// search for a value inside this collection using Comparer function
    // - if the collection was created with loCreateUniqueIndex, will use
    // an internal hash table for O(1) efficient lookup - aSortAs could be set
    // in Collections.NewPlainList<> to hash the first field of a record instead
    // of the whole collection item
    // - if the collection is sorted (i.e. AddSorted was used, or Sort was
    // called after Add) will perform fast O(log(n)) binary search
    // - on a non-sorted collection, will make O(n) comparisons with the value
    // - if customcompare is set, a O(n) comparison lookup will be done
    // - for a brute force RTTI-based search, see IndexOf()
    function Find(const value: T; customcompare: TDynArraySortCompare = nil): PtrInt;
    /// allows to iterate over a generic collection of a specific type
    // - we redefined our own TSynEnumerator<T> record type which is much faster
    // than using classes or interfaces, and provide very readable code:
    // ! var i: integer;
    // !     list: IList<integer>;
    // ! begin
    // !   list := Collections.NewList<integer>;
    // !   for i := 1 to 20 do // populate with some data
    // !     list.Add(i);
    // !   for i in list do    // use an enumerator - safe and clean
    // !     writeln(i);
    function GetEnumerator: TSynEnumerator<T>;
    /// allows to iterate over a range of the collection
    // - returned iterator will efficiently browse the items data in-place:
    // ! for i in list.Range do         // = for i in list do (all data)
    // ! for i in list.Range(10) do     // items 10..Count-1
    // ! for i in list.Range(0, 10) do  // first 0..9 items
    // ! for i in list.Range(10, 20) do // items 10..29 - truncated if Count<30
    // ! for i in list.Range(-10) do    // last Count-10..Count-1 items
    function Range(Offset: PtrInt = 0; Limit: PtrInt = 0): TSynEnumerator<T>;
    /// low-level pointer over the first item of the collection
    // - can be nil if there is no item stored yet
    // - could be used to quickly lookup all items of the array, using Count:
    // ! var pi: PInteger; ...
    // !   pi := list.First;        // fastest method
    // !   for i := 1 to list.Count do begin
    // !     writeln(pi^);
    // !     inc(pi);
    // !   end;
    function First: pointer;
    /// returns a dynamic array containing data of this collection
    // - is a convenient way to consume such a list as regular SOA parameters
    // - Offset/Limit could be used to create a new dynamic array with some part
    // of the existing content (Offset<0 meaning from the end):
    // ! a := list.AsArray;         // whole data assigned with refcount
    // ! a := list.AsArray(10);     // items 10..Count-1
    // ! a := list.AsArray(0, 10);  // first 0..9 items
    // ! a := list.AsArray(10, 20); // items 10..29 - truncated if Count<30
    // ! a := list.AsArray(-10);    // last Count-10..Count-1 items
    function AsArray(Offset: PtrInt = 0; Limit: PtrInt = 0): TArray<T>;
    /// high-level access to the stored values from their associated indexes
    // - raise ESynList if the supplied index is out of range
    // - SetItem() will raise ESynList if loCreateUniqueIndex is defined
    // - is the default propery so that IList<T> could be used as an array:
    // !   for i := 0 to list.Count - 1 do // regular Items[] access
    // !     writeln(list[i]);
    // - note that using an enumerator is faster than using this property within
    // a loop, since TSynEnumerator<T> is a record which can be inlined
    property Items[ndx: PtrInt]: T
      read GetItem write SetItem; default;
    /// returns the number of items actually stored
    // - you can also set the Count value then fill it with Items[]
    property Count: PtrInt
      read GetCount write SetCount;
    /// returns the internal array capacity
    property Capacity: PtrInt
      read GetCapacity write SetCapacity;
    /// the current comparison function, used e.g. for Sort() or Find()
    // - will be assigned by default from RTTI and the loCaseInsensitive option
    property Comparer: TDynArraySortCompare
      read GetComparer write SetComparer;
    /// low-level access to the internal TDynArray wrapper
    // - you can use e.g. Data.SaveToJson/SaveTo and
    // Data.LoadFromJson/LoadFromBinary
    function Data: PDynArray;
  end;

  /// abstract parent of TSynListSpecialized<T> to reduce code size
  // - contains all fields and methods not explicitly related to type T
  TSynListAbstract = class(TInterfacedObject)
  protected
    fCount: PtrInt;  // external TDynArray count
    fValue: pointer; // holds the actual dynamic array of <T>
    fDynArray: TDynArray;
    fOptions: TListOptions;
    fHasher: PDynArrayHasher;
    function DoPop(var dest; opt: TListPop): boolean;
    function DoRemove(const value): boolean;
    function DoAdd(const value): PtrInt;
    function DoAddSorted(const value; wasadded: PBoolean): integer;
    procedure DoInsert(ndx: PtrInt; const value);
    function DoFind(const value; customcompare: TDynArraySortCompare): PtrInt;
    procedure RaiseGetItem(ndx: PtrInt);
    procedure CanSetItem(ndx: PtrInt);
    procedure NewEnumerator(var state: TSynEnumeratorState); overload;
    procedure NewEnumerator(var state: TSynEnumeratorState;
      Offset, Limit: PtrInt); overload;
    // some property accessors
    function GetCount: PtrInt;
    procedure SetCount(value: PtrInt);
    function GetCapacity: PtrInt;
    procedure SetCapacity(value: PtrInt);
    function GetComparer: TDynArraySortCompare;
    procedure SetComparer(customcompare: TDynArraySortCompare);
  public
    /// initialize the array storage, specifying dynamic array type
    // - here aItemTypeInfo is required to be specified, since may not match T
    // - you can provide the dynamic array TypeInfo() of T if the types are too
    // complex, or not already registered to mormot.core.rtti
    // - if aSortAs is ptNone, will guess the comparison/sort function from RTTI
    // - we defined a plain method and not a constructor since it is never
    // called as constuctor, but via Collections.NewList<T> class factory or -
    // on if needed, from TSynListSpecialized<T>.Create
    procedure Init(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; aSortAs: TRttiParserType);
    /// finalize the array storage, mainly the internal TDynArray
    destructor Destroy; override;
    /// IList<> method to delete one item inside the collection from its index
    function Delete(ndx: PtrInt): boolean;
    /// IList<> method to delete all stored items
    procedure Clear;
    /// IList<> method to reverse all collection items, in place
    procedure Reverse;
    /// IList<> method to sort the collection items
    procedure Sort(customcompare: TDynArraySortCompare = nil); overload;
    /// IList<> method to sort a collection range
    procedure Sort(start, stop: integer; customcompare: TDynArraySortCompare = nil); overload;
    /// IList<> method to sort the collection items using an external lookup array
    procedure Sort(var indexes: TIntegerDynArray;
      customcompare: TDynArraySortCompare = nil); overload;
    /// IList<> method to sort the collection, using a comparison method
    procedure Sort(const customcompare: TOnDynArraySortCompare;
      descending: boolean = false); overload;
    /// IList<> method returning true if Sort() or AddSorted() have been used
    function Sorted: boolean;
    /// low-level IList<> method to access the first item of the collection
    function First: pointer;
    /// IList<> method to return the number of items actually stored
    property Count: PtrInt
      read fCount write SetCount;
    /// IList<> method to return the internal array capacity
    property Capacity: PtrInt
      read GetCapacity write SetCapacity;
    /// low-level IList<> method to access to the internal TDynArray wrapper
    function Data: PDynArray;
  end;

  /// generics-based collection storage
  // - high level wrapper around our regular TDynArray implementing IList<T>
  // - main factory is Collections.NewList<T> class function, which returns a
  // IList<T> interface for reusing most class specializations: you should
  // NOT have to define a TSynListSpecialized<T> instance anywhere
  TSynListSpecialized<T> = class(TSynListAbstract, IList<T>)
  protected
    // some property accessors
    function GetItem(ndx: PtrInt): T;
    procedure SetItem(ndx: PtrInt; const value: T);
  public
    /// internal constructor to create an IList<T> instance from RTTI
    // - you should rather use Collections.NewList or Collections.NewPlainList
    // - used only to circumvent FPC internal error 2010021502 on x86_64/aarch64
    // - root cause seems comes from T through another generic method
    // - direct specialization like Collections.NewList<integer> works fine,
    // but cascaded generics like TTestCoreCollections.TestOne<T> need this:
    // ! {$ifdef FPC_64}
    // ! li := TSynListSpecialized<T>.Create;
    // ! {$else}
    // ! li := Collections.NewList<T>;
    // ! {$endif FPC_64}
    constructor Create(aOptions: TListOptions = [];
      aSortAs: TRttiParserType = ptNone; aDynArrayTypeInfo: PRttiInfo = nil;
      aItemTypeInfo: PRttiInfo = nil); overload;
    /// IList<T> method to append a new value to the collection
    function Add(const value: T): PtrInt;
    /// IList<T> method to insert a new value to the collection
    procedure Insert(ndx: PtrInt; const value: T);
    /// IList<T> method to get and remove the last item stored in the collection
    function Pop(var dest: T; opt: TListPop): boolean;
    /// IList<T> method for default search for a value inside this collection
    function IndexOf(const value: T): PtrInt;
    /// IList<T> method for (sorted) search using a comparison function
    function Find(const value: T; customcompare: TDynArraySortCompare = nil): PtrInt;
    /// IList<> method to delete one item inside the collection from its value
    function Remove(const value: T): boolean;
    /// IList<T> method to search and add an item inside a sorted collection
    function AddSorted(const value: T; wasadded: PBoolean = nil): integer;
    /// IList<T> method to return a dynamic array of this collection items
    function AsArray(Offset: PtrInt = 0; Limit: PtrInt = 0): TArray<T>;
    /// IList<T> method to iterate over a generic collection
    function GetEnumerator: TSynEnumerator<T>;
    /// IList<T> method to iterate over some range of the generic collection
    function Range(Offset: PtrInt = 0; Limit: PtrInt = 0): TSynEnumerator<T>;
  end;




{ ************** JSON-aware IKeyValue<> Dictionary Storage }

type
  /// exception class raised by TSynKeyValueSpecialized<TKey, TValue>
   ESynKeyValue = class(ESynException);

  /// gives access to a generics-based dictionary holding key/value pairs
  // - as generated by Collections.NewKeyValue<TKey, TValue> main factory
  // - defined as an interface for automatic memory management
  // - this instance is thread-safe thanks to an internal TSynDictionary
  IKeyValue<TKey, TValue> = interface
    // some property accessors
    function GetItem(const key: TKey): TValue;
    procedure SetItem(const key: TKey; const value: TValue);
    function GetCapacity: integer;
    procedure SetCapacity(value: integer);
    function GetTimeOutSeconds: cardinal;
    procedure SetTimeOutSeconds(value: cardinal);
    /// add a key/value pair to be unique
    // - raise an ESynKeyValue if key was already set
    // - use default Items[] property to add or replace a key/value pair
    procedure Add(const key: TKey; const value: TValue);
    /// add a key/value pair if key is not existing
    // - returns true if was added, false if key was already set
    // - use default Items[] property to add or replace a key/value pair
    function TryAdd(const key: TKey; const value: TValue): boolean;
    /// search a key and return the associated value
    // - returns true if the key was found, false otherwise
    function TryGetValue(const key: TKey; var value: TValue): boolean;
    /// search a key and return the associated value or a supplied default
    function GetValueOrDefault(const key: TKey; const defaultValue: TValue): TValue;
    /// remove a key/value pair
    // - returns true if the entry was deleted, false if key was not found
    function Remove(const key: TKey): boolean;
    /// search a key, get the associated value, then delete the key/value pair
    function Extract(const key: TKey; var value: TValue): boolean;
    /// search for a key/value pair from a key
    // - returns true if the key was found, false otherwise
    function ContainsKey(const key: TKey): boolean;
    /// search for a key/value pair from a value
    // - returns true if the value was found, false otherwise
    function ContainsValue(const value: TValue): boolean;
    /// search and delete all deprecated items according to TimeoutSeconds
    // - returns how many items have been deleted
    // - you can call this method very often: it will ensure that the
    // search process will take place at most once every second
    function DeleteDeprecated: integer;
    /// delete all stored key/value pairs
    procedure Clear; overload;
    /// returns the number of key/value pairs actually stored
    function Count: integer;
    /// high-level access to the stored values from their associated keys
    // - GetItem() raise an ESynKeyValue if the key is not available, unless
    // kvoDefaultIfNotFound option was set- use TryGetValue() if you want to
    // detect (without any exception) any non available key
    // - SetItem() will add the value if key is not existing, or replace it
    property Items[const key: TKey]: TValue
      read GetItem write SetItem; default;
    /// returns the internal TSynDictionary capacity
    property Capacity: integer
      read GetCapacity write SetCapacity;
    /// returns the TimeOutSeconds parameter, as specified to NewKeyValue<>
    // - warning: setting a new timeout will clear all previous content
    property TimeOutSeconds: cardinal
      read GetTimeOutSeconds write SetTimeOutSeconds;
    /// low-level access to the internal TSynDictionary storage
    // - which handles a lot of other useful methods not included as generics
    // to reduce the executable code size
    // - you can use e.g. Data.SaveToJson/SaveToBinary and
    // Data.LoadFromJson/LoadFromBinary
    function Data: TSynDictionary;
  end;

  /// how TSynKeyValueSpecialized<TKey, TValue>.Create() will handle its storage
  TSynKeyValueOptions = set of (
    kvoKeyCaseInsensitive,
    kvoThreadSafe,
    kvoDefaultIfNotFound);

  /// stack parameters to ease TSynKeyValueSpecialized<TKey, TValue> creation
  TNewSynKeyValueContext = record
    Options: TSynKeyValueOptions;
    KeyArrayTypeInfo, KeyItemTypeInfo: PRttiInfo;
    ValueArrayTypeInfo, ValueItemTypeInfo: PRttiInfo;
    Timeout: cardinal;
    Compress: TAlgoCompress;
    Hasher: THasher;
  end;

  /// abstract parent of TSynKeyValueSpecialized<TKey, TValue> to reduce code size
  // - contains all fields and methods not explicitly related to TKey/TValue
  TSynKeyValueAbstract = class(TInterfacedObject)
  protected
    fData: TSynDictionary;
    fKeyTypeInfo, fValueTypeInfo: PRttiInfo;
    fOptions: TSynKeyValueOptions;
    procedure AddOne(const key, value);
    procedure GetOne(const key; var value);
    function GetCapacity: integer;
    procedure SetCapacity(value: integer);
    function GetTimeOutSeconds: cardinal;
    procedure SetTimeOutSeconds(value: cardinal);
  public
    /// initialize the dictionary storage, specifyng dynamic array keys/values
    // - main factory is Collections.NewKeyValue<TKey, TValue> class function,
    // which returns a IKeyValue<> interface for reusing most class specializations:
    // you should NOT call any TSynKeyValueSpecialized<> constructor anywhere
    constructor Create(const aContext: TNewSynKeyValueContext); reintroduce; virtual;
    /// finalize the dictionary storage
    destructor Destroy; override;
    /// IKeyValue<> method to search and delete all deprecated items
    function DeleteDeprecated: integer;
    /// IKeyValue<> method to delete all stored key/value pairs
    procedure Clear; overload;
    /// IKeyValue<> method to get the number of key/value pairs actually stored
    function Count: integer;
    /// IKeyValue<> method to get the internal TSynDictionary capacity
    property Capacity: integer
      read GetCapacity write SetCapacity;
    /// IKeyValue<> method to get the TimeOutSeconds param of NewKeyValue<>
    // - warning: setting a new timeout will clear all previous content
    property TimeOutSeconds: cardinal
      read GetTimeOutSeconds write SetTimeOutSeconds;
    /// low-level IKeyValue<> method to get the internal TSynDictionary storage
    function Data: TSynDictionary;
    /// low-level IKeyValue<> method to get the NewKeyValue<> TSynKeyValueOptions
    property Options: TSynKeyValueOptions
      read fOptions;
    /// low-level TypeInfo(TKey) access
    property KeyTypeInfo: PRttiInfo
      read fKeyTypeInfo;
    /// low-level TypeInfo(TValue) access
    property ValueTypeInfo: PRttiInfo
      read fValueTypeInfo;
  end;

  /// thread-safe generics-based dictionary holding key/value pairs
  // - is a high level wrapper around our regular TSynDictionary
  // - main factory is Collections.NewKeyValue<TKey, TValue> class function, which
  // returns a IKeyValue<> interface for reusing most class specializations: you
  // should NOT directly use a TSynKeyValueSpecialized<> anywhere
  TSynKeyValueSpecialized<TKey, TValue> = class(
    TSynKeyValueAbstract, IKeyValue<TKey, TValue>)
  protected
    // some property accessors
    function GetItem(const key: TKey): TValue;
    procedure SetItem(const key: TKey; const value: TValue);
  public
    /// IKeyValue<> method to add an unique key/value pair
    procedure Add(const key: TKey; const value: TValue);
    /// IKeyValue<> method to add a key/value pair if key is not existing
    function TryAdd(const key: TKey; const value: TValue): boolean;
    /// IKeyValue<> method to search a key and return its associated value
    function TryGetValue(const key: TKey; var value: TValue): boolean;
    /// IKeyValue<> method to search a key or a supplied default
    function GetValueOrDefault(const key: TKey;
      const defaultValue: TValue): TValue;
    /// IKeyValue<> method to remove a key/value pair
    function Remove(const key: TKey): boolean;
    /// IKeyValue<> method to search a key/value, then delete the pair
    function Extract(const key: TKey; var value: TValue): boolean;
    /// IKeyValue<> method to search for a key/value pair from a key
    function ContainsKey(const key: TKey): boolean;
    /// IKeyValue<> method to search for a key/value pair from a value
    function ContainsValue(const value: TValue): boolean;
    /// high-level IKeyValue<> method to get the stored values from their keys
    property Items[const key: TKey]: TValue
      read GetItem write SetItem; default;
  end;



{ ************ Collections Factory for IList<> and IKeyValue<> Instances }

{$ifdef HASGETTYPEKIND} // our specialization rely on new compiler intrinsics

  {$ifndef NOSPECIALIZE} // if not disabled for the project

    // enable generics cold compilation in mormot.core.collections unit
    {$define SPECIALIZE_ENABLED}

    // enable generics cold compilation of THash128-TGuid and THash256/THash612
    {$define SPECIALIZE_HASH}

    // small byte/word are not useful in dictionaries (use integer instead)
    // so are not pre-compiled by default - this conditional generates them
    // - this affects only IKeyValue<> not IList<> which specializes byte/word
    {.$define SPECIALIZE_SMALL}

    // WideString are slow - RawUtf8 or UnicodeString are to be used instead -
    // so are not pre-compiled by default - this conditional generates them
    {.$define SPECIALIZE_WSTRING}

  {$endif NOSPECIALIZE}

{$endif HASGETTYPEKIND}

type
  /// various factories to create instances of our generic collections
  // - this is main entry point of mormot.core.collections unit
  // - you should never call TSynListSpecialized<T>.Create nor
  // TSynKeyValueSpecialized<TKey, TValue>.Create constructors, but the static
  // Collections.NewList<T> and Collections.NewKeyValue<TKey, TValue> methods
  Collections = class
  protected
  {$ifdef SPECIALIZE_ENABLED}
    {$ifdef FPC}
    const
      tkLString = tkAString; // circumvent FPC RTTI incompatibility
    {$endif FPC}
    // dedicated factories for most common TSynListSpecialized<T> types
    class procedure NewOrdinal(aSize: integer; aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    class procedure NewFloat(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    class procedure NewLString(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    {$ifdef SPECIALIZE_WSTRING}
    class procedure NewWString(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    {$endif SPECIALIZE_WSTRING}
    class procedure NewUString(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    class procedure NewInterface(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    class procedure NewVariant(aOptions: TListOptions;
      aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result); static;
    // dedicated factories for most common TSynKeyValueSpecialized<> types
    class procedure NewOrdinalOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeKey, aSizeValue: integer; var result); static;
    class procedure NewOrdinalLString(const aContext: TNewSynKeyValueContext;
      aSizeKey: integer; var result); static;
    class procedure NewOrdinalUString(const aContext: TNewSynKeyValueContext;
      aSizeKey: integer; var result); static;
    class procedure NewOrdinalInterface(const aContext: TNewSynKeyValueContext;
      aSizeKey: integer; var result); static;
    class procedure NewOrdinalVariant(const aContext: TNewSynKeyValueContext;
      aSizeKey: integer; var result); static;
    {$ifdef SPECIALIZE_WSTRING}
    class procedure NewOrdinalWString(const aContext: TNewSynKeyValueContext;
      aSizeKey: integer; var result); static;
    class procedure NewWStringOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeValue: integer; var result); static;
    class procedure NewWStringManaged(const aContext: TNewSynKeyValueContext;
      aValue: TTypeKind; var result); static;
    {$endif SPECIALIZE_WSTRING}
    class procedure NewLStringOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeValue: integer; var result); static;
    class procedure NewLStringManaged(const aContext: TNewSynKeyValueContext;
      aValue: TTypeKind; var result); static;
    class procedure NewUStringOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeValue: integer; var result); static;
    class procedure NewUStringManaged(const aContext: TNewSynKeyValueContext;
      aValue: TTypeKind; var result); static;
    class procedure NewInterfaceOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeValue: integer; var result); static;
    class procedure NewInterfaceManaged(const aContext: TNewSynKeyValueContext;
      aValue: TTypeKind; var result); static;
    class procedure NewVariantOrdinal(const aContext: TNewSynKeyValueContext;
      aSizeValue: integer; var result); static;
    class procedure NewVariantManaged(const aContext: TNewSynKeyValueContext;
      aValue: TTypeKind; var result); static;
    // the RTTI is too complex -> should call NewPlain*<>() methods instead
    class function RaiseUseNewPlainList(aItemTypeInfo: PRttiInfo): pointer; static;
    class function RaiseUseNewPlainKeyValue(
      const aContext: TNewSynKeyValueContext): pointer; static;
  {$endif SPECIALIZE_ENABLED}
  public
    /// generate a new IList<T> instance for most simple types
    // - use this factory method instead of plain TSynListSpecialized<T>.Create
    // so that the types will be specifialized and compiled once in this unit
    // - by default, string values would be searched following exact case,
    // unless the loCaseInsensitive option is set
    // - raise ESynList if T type is too complex: use NewPlainList<T>() instead
    // - not inlined since it is of little benefit
    class function NewList<T>(aOptions: TListOptions = [];
      aDynArrayTypeInfo: PRttiInfo = nil): IList<T>; static;
    /// generate a new IList<T> instance with exact TSynListSpecialized<T>
    // - to be called for complex types (e.g. managed records) when
    // NewList<T> fails and triggers ESynList
    // - by default, string values would be searched following exact case,
    // unless the loCaseInsensitive option is set
    // - you can provide the dynamic array TypeInfo() of T if the types are too
    // complex, or not already registered to mormot.core.rtti
    // - if aSortAs is ptNone, will guess the comparison/sort function from RTTI
    // but you can force one e.g. to sort/compare/hash using a record first field
    class function NewPlainList<T>(aOptions: TListOptions = [];
      aDynArrayTypeInfo: PRttiInfo = nil; aSortAs: TRttiParserType = ptNone): IList<T>;
        static; {$ifdef FPC} inline; {$endif}
    /// generate a new IKeyValue<TKey, TValue> instance
    // - use this factory method instead of TSynKeyValueSpecialized<>.Create
    // so that the types will be specifialized and compiled once in this unit
    // - you can provide the dynamic array TypeInfo() of T if the types are too
    // complex, or not already registered to mormot.core.rtti
    // - by default, this instance won't be thread-safe unless the kvoThreadSafe
    // option is forced, so that process is protected with a TSynLocker mutex
    // - by default, string keys would be searched following exact case, unless
    // the kvoKeyCaseInsensitive option is set
    // - you can set an optional timeout period, in seconds - you should call
    // DeleteDeprecated periodically to search for deprecated items
    // - raise ESynKeyValue if T type is too complex: use NewPlainList<T>() instead
    // - inlining does (little) sense even if most parameters would be inlined
    class function NewKeyValue<TKey, TValue>(aOptions: TSynKeyValueOptions = [];
      aKeyDynArrayTypeInfo: PRttiInfo = nil; aValueDynArrayTypeInfo: PRttiInfo = nil;
      aTimeoutSeconds: cardinal = 0; aCompressAlgo: TAlgoCompress = nil;
      aHasher: THasher = nil): IKeyValue<TKey, TValue>; static;
    /// generate a new IKeyValue<TKey, TValue> instance with exact
    // TSynKeyValueSpecialized<TKey, TValue>
    // - to be called for complex types (e.g. managed records) when
    // NewKeyValue<TKey, TValue> fails and triggers ESynKeyValue
    class function NewPlainKeyValue<TKey, TValue>(aOptions: TSynKeyValueOptions = [];
      aKeyDynArrayTypeInfo: PRttiInfo = nil; aValueDynArrayTypeInfo: PRttiInfo = nil;
      aTimeoutSeconds: cardinal = 0; aCompressAlgo: TAlgoCompress = nil;
      aHasher: THasher = nil): IKeyValue<TKey, TValue>;
        static; {$ifdef FPC} inline; {$endif}
  end;



implementation

{ ************** JSON-aware IList<> List Storage }

{ TSynEnumerator }

function TSynEnumerator<T>.MoveNext: boolean;
var
  c: PtrUInt; // to enhance code generation
begin
  {$ifdef NOSIZEOFT}
  c := fState.ItemSize + fState.Current;
  {$else}
  c := fState.Current + PtrUInt(SizeOf(T));
  {$endif NOSIZEOFT}
  fState.Current := c;
  result := c < fState.After; // false if fCurrent=fItemSize=fAfter=0
end;

function TSynEnumerator<T>.GetEnumerator: TSynEnumerator<T>;
begin
  result := self; // just a copy of 3 PtrInt
end;

function TSynEnumerator<T>.DoGetCurrent: T;
begin
  result := PT(fState.Current)^; // faster than fDynArray^.ItemCopy()
end;


{ TSynListAbstract }

procedure TSynListAbstract.Init(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; aSortAs: TRttiParserType);
var
  r: PRttiInfo;
begin
  fOptions := aOptions;
  r := aDynArrayTypeInfo;
  if r = nil then // use RTTI to guess the associated TArray<T> type
    r := TypeInfoToDynArrayTypeInfo(aItemTypeInfo, {exact=}false);
  if (r = nil) or
     (r^.Kind <> rkDynArray) then
     raise ESynList.CreateUtf8('%.Create: % should be a dynamic array of T',
       [self, r^.Name^]);
  aSortAs := fDynArray.InitSpecific(r, fValue, aSortAs, // aSortAs=ptNone->RTTI
    @fCount, loCaseInsensitive in fOptions);
  if (fDynArray.Info.ArrayRtti = nil) or
     ((aDynArrayTypeInfo <> nil) and
      (fDynArray.Info.ArrayRtti.Kind <> aItemTypeInfo^.Kind))  then
    raise ESynList.CreateUtf8('%.Create: T does not match %',
      [self, aDynArrayTypeInfo.RawName]);
  if loNoFinalize in fOptions then
    fDynArray.NoFinalize := true; // be careful!
  if loCreateUniqueIndex in fOptions then
  begin
    fHasher := AllocMem(SizeOf(fHasher^));
    fHasher^.InitSpecific(@fDynArray, aSortAs, loCaseInsensitive in fOptions, nil);
  end;
end;

destructor TSynListAbstract.Destroy;
begin
  inherited Destroy;
  fDynArray.Clear;
  if fHasher <> nil then
    Dispose(fHasher);
end;

function TSynListAbstract.Delete(ndx: PtrInt): boolean;
begin
  if fHasher <> nil then
    raise ESynList.CreateUtf8('%.Delete(%) is not allowed  with ' +
      'loCreateUniqueIndex: use Remove()', [self, ndx]);
  result := fDynArray.Delete(ndx);
end;

function TSynListAbstract.DoPop(var dest; opt: TListPop): boolean;
begin
  if fHasher <> nil then
    raise ESynList.CreateUtf8(
      '%.Pop() is not compatible with loCreateUniqueIndex', [self]);
  if popFromHead in opt then
    if popPeek in opt then
      result := fDynArray.PeekHead(dest)
    else
      result := fDynArray.PopHead(dest)
  else if popPeek in opt then
    result := fDynArray.Peek(dest)
  else
    result := fDynArray.Pop(dest);
end;

function TSynListAbstract.DoRemove(const value): boolean;
var
  ndx: PtrInt;
  h: PDynArrayHasher;
begin
  h := fHasher;
  if h <> nil then
    ndx := h^.FindBeforeDelete(@value)
  else
    ndx := fDynArray.Find(value);
  result := (ndx >= 0) and
            fDynArray.Delete(ndx);
end;

function TSynListAbstract.DoAdd(const value): PtrInt;
var
  added: boolean;
  v: PAnsiChar;
  n: PtrInt;
  h: PDynArrayHasher;
begin
  h := fHasher;
  if h <> nil then
  begin
    result := h^.FindBeforeAdd(@value, added, h^.HashOne(@value));
    if not added then
      exit; // already existing -> just return previous value index
  end;
  v := fValue;
  n := fCount;
  if (v = nil) or
     (n = PDALen(v -_DALEN)^ + _DAOFF) then
    // let TDynArray handle the capacity
    fDynArray.Count := n + 1
  else
    inc(fCount);
  fDynArray.ItemCopy(@value, PAnsiChar(fValue) + n * fDynArray.Info.Cache.ItemSize);
  result := n;
end;

function TSynListAbstract.DoAddSorted(const value; wasadded: PBoolean): integer;
begin
  if fHasher <> nil then
    raise ESynList.CreateUtf8('%.AddSorted() is not allowed  with ' +
      'loCreateUniqueIndex: use Add()', [self]);
  result := fDynArray.FastLocateOrAddSorted(value, wasadded);
end;

procedure TSynListAbstract.DoInsert(ndx: PtrInt; const value);
begin
  if fHasher <> nil then
    raise ESynList.CreateUtf8('%.Insert(%) is not allowed with ' +
      'loCreateUniqueIndex: use Add()', [self, ndx]);
  fDynArray.Insert(ndx, value);
end;

function TSynListAbstract.DoFind(const value;
  customcompare: TDynArraySortCompare): PtrInt;
var
  h: PDynArrayHasher;
begin
  h := fHasher;
  if h <> nil then
    result := h^.Find(@value, h^.HashOne(@value))
  else
    result := fDynArray.Find(value, customcompare);
end;

procedure TSynListAbstract.RaiseGetItem(ndx: PtrInt);
begin
  raise ESynList.CreateUtf8('%.GetItem(%): out of range (Count=%)',
    [self, ndx, fCount]);
end;

procedure TSynListAbstract.CanSetItem(ndx: PtrInt);
begin
  if fHasher <> nil then
    raise ESynList.CreateUtf8('%.SetItem(%) is not allowed with ' +
      'loCreateUniqueIndex: use Remove() then Add()', [self, ndx]);
  if PtrUInt(ndx) >= PtrUInt(fCount) then
    raise ESynList.CreateUtf8('%.SetItem(%): out of range (Count=%)',
      [self, ndx, fCount]);
end;

function TSynListAbstract.GetCount: PtrInt;
begin
  result := fCount;
end;

procedure TSynListAbstract.SetCount(value: PtrInt);
var
  forcehash: boolean;
begin
  forcehash := (fHasher <> nil) and
               (value < fCount);
  fDynArray.Count := value; // will resize the dynamic array
  if forcehash then
    fHasher^.ReHash({forced=}true);
end;

function TSynListAbstract.GetCapacity: PtrInt;
begin
  result := fDynArray.Capacity;
end;

procedure TSynListAbstract.SetCapacity(value: PtrInt);
var
  forcehash: boolean;
begin
  forcehash := (fHasher <> nil) and
               (value < fCount);
  fDynArray.Capacity := value; // don't change Count, just dynamic array length
  if forcehash then
    fHasher^.ReHash({forced=}true);
end;

procedure TSynListAbstract.Clear;
begin
  fDynArray.Clear;
  if fHasher <> nil then
    fHasher^.ReHash({forced=}true);
end;

procedure TSynListAbstract.Reverse;
begin
  fDynArray.Reverse;
  if fHasher <> nil then
    fHasher^.ReHash({forced=}true);
end;

function TSynListAbstract.GetComparer: TDynArraySortCompare;
begin
  result := fDynArray.Compare;
end;

procedure TSynListAbstract.SetComparer(customcompare: TDynArraySortCompare);
begin
  fDynArray.Compare := customcompare;
end;

procedure TSynListAbstract.Sort(customcompare: TDynArraySortCompare);
begin
  fDynArray.Sort(customcompare);
  if fHasher <> nil then
    fHasher^.ReHash({forced=}true);
end;

procedure TSynListAbstract.Sort(start, stop: integer;
  customcompare: TDynArraySortCompare);
begin
  fDynArray.SortRange(start, stop, customcompare);
  if fHasher <> nil then
    fHasher^.ReHash({forced=}true);
end;

procedure TSynListAbstract.Sort(var indexes: TIntegerDynArray;
  customcompare: TDynArraySortCompare);
begin
  fDynArray.CreateOrderedIndex(indexes, customcompare);
end;

procedure TSynListAbstract.Sort(const customcompare: TOnDynArraySortCompare;
  descending: boolean);
begin
  fDynArray.Sort(customcompare, descending);
  if fHasher <> nil then
    fHasher^.ReHash({forced=}true);
end;

function TSynListAbstract.Sorted: boolean;
begin
  result := fDynArray.Sorted;
end;

function TSynListAbstract.First: pointer;
begin
  result := fValue;
end;

function TSynListAbstract.Data: PDynArray;
begin
  result := @fDynArray;
end;

procedure TSynListAbstract.NewEnumerator(var state: TSynEnumeratorState);
var
  s: PtrUInt;
begin
  state.Current := PtrUInt(fValue);
  if state.Current = 0 then
  begin
    {$ifdef NOSIZEOFT} state.ItemSize := 0; {$endif}
    state.After := 0; // ensure MoveNext=false
    exit;
  end;
  s := fDynArray.Info.Cache.ItemSize;
  {$ifdef NOSIZEOFT} state.ItemSize := s; {$endif}
  state.After := state.Current + s * PtrUInt(fCount);
  dec(state.Current, s);
end;

procedure TSynListAbstract.NewEnumerator(var state: TSynEnumeratorState;
  Offset, Limit: PtrInt);
var
  s: PtrUInt;
begin
  if Offset < 0 then
  begin
    inc(Offset, fCount);
    if Offset < 0 then
      Offset := 0;
  end;
  state.Current := PtrUInt(fValue);
  if (state.Current = 0) or
     (Offset >= fCount) then
  begin
    {$ifdef NOSIZEOFT} state.ItemSize := 0; {$endif}
    state.After := 0;  // ensure MoveNext=false
    exit;
  end;
  if Limit = 0 then
    Limit := fCount;
  s := fCount - Offset;
  if Limit > PtrInt(s) then
    Limit := s;
  s := fDynArray.Info.Cache.ItemSize;
  {$ifdef NOSIZEOFT} state.ItemSize := s; {$endif}
  inc(state.Current, s * PtrUInt(Offset));
  state.After := state.Current + s * PtrUInt(Limit);
  dec(state.Current, s);
end;


{ TSynListSpecialized }

constructor TSynListSpecialized<T>.Create(aOptions: TListOptions;
  aSortAs: TRttiParserType; aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo);
begin
  if aItemTypeInfo = nil then
    aItemTypeInfo := TypeInfo(T);
  Init(aOptions, aDynArrayTypeInfo, aItemTypeInfo, aSortAs);
end;

function TSynListSpecialized<T>.GetItem(ndx: PtrInt): T;
begin
  if PtrUInt(ndx) >= PtrUInt(fCount) then
    RaiseGetItem(ndx);
  result := TArray<T>(fValue)[ndx];
end;

procedure TSynListSpecialized<T>.SetItem(ndx: PtrInt; const value: T);
begin
  CanSetItem(ndx);// also check loCreateUniqueIndex
  TArray<T>(fValue)[ndx] := value;
end;

function TSynListSpecialized<T>.GetEnumerator: TSynEnumerator<T>;
begin
  NewEnumerator(result.fState);
end;

function TSynListSpecialized<T>.Range(Offset, Limit: PtrInt): TSynEnumerator<T>;
begin
  NewEnumerator(result.fState, Offset, Limit);
end;

function TSynListSpecialized<T>.Add(const value: T): PtrInt;
begin
  result := DoAdd(value);
end;

procedure TSynListSpecialized<T>.Insert(ndx: PtrInt; const value: T);
begin
  DoInsert(ndx, value);
end;

function TSynListSpecialized<T>.Pop(var dest: T; opt: TListPop): boolean;
begin
  result := DoPop(dest, opt);
end;

function TSynListSpecialized<T>.IndexOf(const value: T): PtrInt;
begin
  result := fDynArray.IndexOf(value, loCaseInsensitive in fOptions)
end;

function TSynListSpecialized<T>.Find(const value: T;
  customcompare: TDynArraySortCompare): PtrInt;
begin
  result := DoFind(value, customcompare);
end;

function TSynListSpecialized<T>.Remove(const value: T): boolean;
begin
  result := DoRemove(value);
end;

function TSynListSpecialized<T>.AddSorted(const value: T; wasadded: PBoolean): integer;
begin
  result := DoAddSorted(value, wasadded);
end;

function TSynListSpecialized<T>.AsArray(Offset, Limit: PtrInt): TArray<T>;
begin
  // assign existing dynamic array instance to TArray<T> result
  fDynArray.SliceAsDynArray(@result, Offset, Limit);
end;



{ ************** JSON-aware IKeyValue<> Dictionary Storage }

{ TSynKeyValueAbstract }

// shared methods leveraged against all TSynKeyValueSpecialized<> to reduce exe code size

constructor TSynKeyValueAbstract.Create(const aContext: TNewSynKeyValueContext);
var
  k, v: PRttiInfo;
begin
  fOptions := aContext.Options;
  fKeyTypeInfo := aContext.KeyItemTypeInfo;
  fValueTypeInfo := aContext.ValueItemTypeInfo;
  // validate or recognize most simple dynamic arrays from its TKey/TValue types
  k := aContext.KeyArrayTypeInfo;
  if k = nil then
    k := TypeInfoToDynArrayTypeInfo(fKeyTypeInfo, {exact=}false);
  if (k = nil) or
     (k ^.Kind <> rkDynArray) then
     raise ESynKeyValue.CreateUtf8('%.Create: % should be an array of TKey',
       [self, k^.Name^]);
  v := aContext.ValueArrayTypeInfo;
  if v = nil then
    v := TypeInfoToDynArrayTypeInfo(fValueTypeInfo, {exact=}false);
  if (v = nil) or
     (v^.Kind <> rkDynArray) then
     raise ESynKeyValue.CreateUtf8('%.Create: % should be an array of TValue',
       [self, v^.Name^]);
  // initialize the associated dictionary
  fData := TSynDictionary.Create(k, v, kvoKeyCaseInsensitive in fOptions,
    aContext.Timeout, aContext.Compress, aContext.Hasher);
  if kvoThreadSafe in fOptions then
    fData.Options := [doSingleThreaded];
  if (fData.Keys.Info.ArrayRtti = nil) or
     ((aContext.KeyArrayTypeInfo <> nil) and
      (fData.Keys.Info.ArrayRtti.Info <> fKeyTypeInfo)) then
    raise ESynKeyValue.CreateUtf8('%.Create: TKey does not match %',
      [self, aContext.KeyArrayTypeInfo^.RawName]);
  if (fData.Values.Info.ArrayRtti = nil) or
     ((aContext.ValueArrayTypeInfo <> nil) and
      (fData.Values.Info.ArrayRtti.Info <> fValueTypeInfo)) then
    raise ESynKeyValue.CreateUtf8('%.Create: TValue does not match %',
      [self, aContext.ValueArrayTypeInfo^.RawName]);
end;

destructor TSynKeyValueAbstract.Destroy;
begin
  inherited Destroy;
  fData.Free;
end;

procedure TSynKeyValueAbstract.AddOne(const key, value);
begin
  if fData.Add(key, value) < 0 then
    raise ESynKeyValue.CreateUtf8('%.Add: duplicated key', [self]);
end;

procedure TSynKeyValueAbstract.GetOne(const key; var value);
begin
  if not fData.FindAndCopy(key, value) then
    if kvoDefaultIfNotFound in fOptions then
      fData.Values.ItemClear(@value)
    else
      raise ESynKeyValue.CreateUtf8('%.GetItem: key not found', [self]);
end;

function TSynKeyValueAbstract.GetCapacity: integer;
begin
  result := fData.Capacity;
end;

procedure TSynKeyValueAbstract.SetCapacity(value: integer);
begin
  fData.Capacity := value;
end;

function TSynKeyValueAbstract.GetTimeOutSeconds: cardinal;
begin
  result := fData.TimeOutSeconds;
end;

procedure TSynKeyValueAbstract.SetTimeOutSeconds(value: cardinal);
begin
  fData.TimeOutSeconds := value;
end;

function TSynKeyValueAbstract.DeleteDeprecated: integer;
begin
  result := fData.DeleteDeprecated;
end;

procedure TSynKeyValueAbstract.Clear;
begin
  fData.DeleteAll;
end;

function TSynKeyValueAbstract.Count: integer;
begin
  result := fData.Count;
end;

function TSynKeyValueAbstract.Data: TSynDictionary;
begin
  result := fData;
end;


{ TSynKeyValueSpecialized<TKey, TValue> }

function TSynKeyValueSpecialized<TKey, TValue>.GetItem(const key: TKey): TValue;
begin
  GetOne(key, result);
end;

procedure TSynKeyValueSpecialized<TKey, TValue>.SetItem(const key: TKey;
  const value: TValue);
begin
  fData.AddOrUpdate(key, value);
end;

procedure TSynKeyValueSpecialized<TKey, TValue>.Add(const key: TKey; const value: TValue);
begin
  AddOne(key, value);
end;

function TSynKeyValueSpecialized<TKey, TValue>.TryAdd(const key: TKey;
  const value: TValue): boolean;
begin
  result := fData.Add(key, value) >= 0;
end;

function TSynKeyValueSpecialized<TKey, TValue>.TryGetValue(const key: TKey;
  var value: TValue): boolean;
begin
  result := fData.FindAndCopy(key, value);
end;

function TSynKeyValueSpecialized<TKey, TValue>.GetValueOrDefault(const key: TKey;
  const defaultValue: TValue): TValue;
begin
  if not fData.FindAndCopy(key, result) then
    result := defaultValue;
end;

function TSynKeyValueSpecialized<TKey, TValue>.Remove(const key: TKey): boolean;
begin
  result := fData.Delete(key) >= 0;
end;

function TSynKeyValueSpecialized<TKey, TValue>.Extract(const key: TKey;
  var value: TValue): boolean;
begin
  result := fData.FindAndExtract(key, value);
end;

function TSynKeyValueSpecialized<TKey, TValue>.ContainsKey(const key: TKey): boolean;
begin
  result := fData.Exists(key);
end;

function TSynKeyValueSpecialized<TKey, TValue>.ContainsValue(const value: TValue): boolean;
begin
  result := fData.ExistsValue(value);
end;



{ ************ Collections Factory for IList<> and IKeyValue<> Instances }

{ Collections }

{$ifdef SPECIALIZE_ENABLED}

// since Delphi XE8 or FPC 3.2: generate the most common type specializations
// in this very unit, to reduce units and executable code size

{$ifdef ISDELPHI} {$HINTS OFF} {$endif}
class function Collections.RaiseUseNewPlainList(aItemTypeInfo: PRttiInfo): pointer;
begin
  raise ESynList.CreateUtf8('Collections.NewList<>: Type is too complex - ' +
    'use Collections.NewPlainList<%> instead', [aItemTypeInfo.RawName]);
    // we tried Delphi' "at ReturnAddress" but disabled to avoid internal errors
end;

class function Collections.RaiseUseNewPlainKeyValue(
  const aContext: TNewSynKeyValueContext): pointer;
begin
  raise ESynKeyValue.CreateUtf8('Collections.NewKeyValue<>: Types are too ' +
    'complex - use Collections.NewPlainKeyValue<%, %> instead',
    [aContext.KeyItemTypeInfo.RawName, aContext.ValueItemTypeInfo.RawName]);
end;
{$ifdef ISDELPHI} {$HINTS ON} {$endif}


// some shared TSynListSpecialized<> which could be reused for IList<>
// - ptNone below will use proper RTTI at runtime for process

class procedure Collections.NewOrdinal(aSize: integer; aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
var
  obj: pointer;
begin
  // IList<T> will assume ordinal parameters are passed in a consistent way
  case aSize of
    1:
      obj := TSynListSpecialized<Byte>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    2:
      obj := TSynListSpecialized<Word>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    4:
      obj := TSynListSpecialized<Integer>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    8:
      obj := TSynListSpecialized<Int64>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynListSpecialized<THash128>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    32:
      obj := TSynListSpecialized<THash256>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    64:
      obj := TSynListSpecialized<THash512>.Create(
        aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainList(aItemTypeInfo);
  end;
  // all IList<T> share the same VMT -> assign once
  IList<Byte>(result) := TSynListSpecialized<Byte>(obj);
end;

class procedure Collections.NewFloat(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
var
  obj: pointer;
begin
  case aItemTypeInfo^.RttiFloat of
    rfSingle:
      obj := TSynListSpecialized<Single>.Create(
        aOptions, ptSingle, aDynArrayTypeInfo, aItemTypeInfo);
    rfDouble:
      obj := TSynListSpecialized<Double>.Create(
        aOptions, ptDouble, aDynArrayTypeInfo, aItemTypeInfo);
  else
    obj := RaiseUseNewPlainList(aItemTypeInfo);
  end;
  IList<Double>(result) := TSynListSpecialized<Double>(obj);
end;

class procedure Collections.NewLString(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
begin
  IList<RawByteString>(result) := TSynListSpecialized<RawByteString>.Create(
    aOptions, ptNone, aDynArrayTypeInfo, aItemTypeInfo); // may be RawUtf8/RawJson
end;

{$ifdef SPECIALIZE_WSTRING}
class procedure Collections.NewWString(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
begin
  IList<WideString>(result) := TSynListSpecialized<WideString>.Create(
    aOptions, aDynArrayTypeInfo, aItemTypeInfo, ptWideString);
end;
{$endif SPECIALIZE_WSTRING}

class procedure Collections.NewUString(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
begin
  IList<UnicodeString>(result) := TSynListSpecialized<UnicodeString>.Create(
    aOptions, ptUnicodeString, aDynArrayTypeInfo, aItemTypeInfo);
end;

class procedure Collections.NewInterface(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
begin
  IList<IInterface>(result) := TSynListSpecialized<IInterface>.Create(
    aOptions, ptInterface, aDynArrayTypeInfo, aItemTypeInfo);
end;

class procedure Collections.NewVariant(aOptions: TListOptions;
  aDynArrayTypeInfo, aItemTypeInfo: PRttiInfo; var result);
begin
  IList<Variant>(result) := TSynListSpecialized<Variant>.Create(
    aOptions, ptVariant, aDynArrayTypeInfo, aItemTypeInfo);
end;


// some shared TSynKeyValueSpecialized<> which could be reused for IKeyValue<>

class procedure Collections.NewOrdinalOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeKey, aSizeValue: integer;
  var result);
var
  obj: pointer;
label
  err;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      case aSizeValue of
        1:
          obj := TSynKeyValueSpecialized<Byte, Byte>.Create(aContext);
        2:
          obj := TSynKeyValueSpecialized<Byte, Word>.Create(aContext);
        4:
          obj := TSynKeyValueSpecialized<Byte, Integer>.Create(aContext);
        8:
          obj := TSynKeyValueSpecialized<Byte, Int64>.Create(aContext);
        {$ifdef SPECIALIZE_HASH}
        16:
          obj := TSynKeyValueSpecialized<Byte, THash128>.Create(aContext);
        {$endif SPECIALIZE_HASH}
      else
        goto err;
      end;
    2:
      case aSizeValue of
        1:
          obj := TSynKeyValueSpecialized<Word, Byte>.Create(aContext);
        2:
          obj := TSynKeyValueSpecialized<Word, Word>.Create(aContext);
        4:
          obj := TSynKeyValueSpecialized<Word, Integer>.Create(aContext);
        8:
          obj := TSynKeyValueSpecialized<Word, Int64>.Create(aContext);
        {$ifdef SPECIALIZE_HASH}
        16:
          obj := TSynKeyValueSpecialized<Word, THash128>.Create(aContext);
        {$endif SPECIALIZE_HASH}
      else
        goto err;
      end;
    {$endif SPECIALIZE_SMALL}
    4:
      case aSizeValue of
        {$ifdef SPECIALIZE_SMALL}
        1:
          obj := TSynKeyValueSpecialized<Integer, Byte>.Create(aContext);
        2:
          obj := TSynKeyValueSpecialized<Integer, Word>.Create(aContext);
        {$endif SPECIALIZE_SMALL}
        4:
          obj := TSynKeyValueSpecialized<Integer, Integer>.Create(aContext);
        8:
          obj := TSynKeyValueSpecialized<Integer, Int64>.Create(aContext);
        {$ifdef SPECIALIZE_HASH}
        16:
          obj := TSynKeyValueSpecialized<Integer, THash128>.Create(aContext);
        {$endif SPECIALIZE_HASH}
      else
err:    obj := RaiseUseNewPlainKeyValue(aContext);
      end;
    8:
      case aSizeValue of
        {$ifdef SPECIALIZE_SMALL}
        1:
          obj := TSynKeyValueSpecialized<Int64, Byte>.Create(aContext);
        2:
          obj := TSynKeyValueSpecialized<Int64, Word>.Create(aContext);
        {$endif SPECIALIZE_SMALL}
        4:
          obj := TSynKeyValueSpecialized<Int64, Integer>.Create(aContext);
        8:
          obj := TSynKeyValueSpecialized<Int64, Int64>.Create(aContext);
        {$ifdef SPECIALIZE_HASH}
        16:
          obj := TSynKeyValueSpecialized<Int64, THash128>.Create(aContext);
        {$endif SPECIALIZE_HASH}
      else
        goto err;
      end;
    {$ifdef SPECIALIZE_HASH}
    16:
      case aSizeValue of
        {$ifdef SPECIALIZE_SMALL}
        1:
          obj := TSynKeyValueSpecialized<THash128, Byte>.Create(aContext);
        2:
          obj := TSynKeyValueSpecialized<THash128, Word>.Create(aContext);
        {$endif SPECIALIZE_SMALL}
        4:
          obj := TSynKeyValueSpecialized<THash128, Integer>.Create(aContext);
        8:
          obj := TSynKeyValueSpecialized<THash128, Int64>.Create(aContext);
        16:
          obj := TSynKeyValueSpecialized<THash128, THash128>.Create(aContext);
      else
        goto err;
      end;
    {$endif SPECIALIZE_HASH}
  else
    goto err;
  end;
  // all IKeyValue<TKey, TValue> share the same VMT -> assign once
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewOrdinalLString(
  const aContext: TNewSynKeyValueContext; aSizeKey: integer; var result);
var
  obj: pointer;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Byte, RawByteString>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Word, RawByteString>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Integer, RawByteString>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Int64, RawByteString>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<THash128, RawByteString>.Create(aContext);
    32:
      obj := TSynKeyValueSpecialized<THash256, RawByteString>.Create(aContext);
    64:
      obj := TSynKeyValueSpecialized<THash512, RawByteString>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

{$ifdef SPECIALIZE_WSTRING}
class procedure Collections.NewOrdinalWString(
  const aContext: TNewSynKeyValueContext; aSizeKey: integer; var result);
var
  obj: pointer;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Byte, WideString>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Word, WideString>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Integer, WideString>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Int64, WideString>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<THash128, WideString>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;
{$endif SPECIALIZE_WSTRING}

class procedure Collections.NewOrdinalUString(
  const aContext: TNewSynKeyValueContext; aSizeKey: integer; var result);
var
  obj: pointer;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Byte, UnicodeString>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Word, UnicodeString>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Integer, UnicodeString>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Int64, UnicodeString>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<THash128, UnicodeString>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewOrdinalInterface(
  const aContext: TNewSynKeyValueContext; aSizeKey: integer; var result);
var
  obj: pointer;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Byte, IInterface>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Word, IInterface>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Integer, IInterface>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Int64, IInterface>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<THash128, IInterface>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewOrdinalVariant(
  const aContext: TNewSynKeyValueContext; aSizeKey: integer; var result);
var
  obj: pointer;
begin
  case aSizeKey of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Byte, Variant>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Word, Variant>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Integer, Variant>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Int64, Variant>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<THash128, Variant>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewLStringOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeValue: integer; var result);
var
  obj: pointer;
begin
  case aSizeValue of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<RawByteString, Byte>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<RawByteString, Word>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<RawByteString, Integer>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<RawByteString, Int64>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<RawByteString, THash128>.Create(aContext);
    32:
      obj := TSynKeyValueSpecialized<RawByteString, THash256>.Create(aContext);
    64:
      obj := TSynKeyValueSpecialized<RawByteString, THash512>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewLStringManaged(
  const aContext: TNewSynKeyValueContext; aValue: TTypeKind; var result);
var
  obj: pointer;
begin
  case aValue of
    tkLString:
      obj := TSynKeyValueSpecialized<RawByteString, RawByteString>.Create(aContext);
    {$ifdef SPECIALIZE_WSTRING}
    tkWString:
      obj := TSynKeyValueSpecialized<RawByteString, WideString>.Create(aContext);
    {$endif SPECIALIZE_WSTRING}
    tkUString:
      obj := TSynKeyValueSpecialized<RawByteString, UnicodeString>.Create(aContext);
    tkInterface:
      obj := TSynKeyValueSpecialized<RawByteString, IInterface>.Create(aContext);
    tkVariant:
      obj := TSynKeyValueSpecialized<RawByteString, Variant>.Create(aContext);
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

{$ifdef SPECIALIZE_WSTRING}
class procedure Collections.NewWStringOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeValue: integer; var result);
var
  obj: pointer;
begin
  case aSizeValue of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<WideString, Byte>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<WideString, Word>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<WideString, Integer>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<WideString, Int64>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<WideString, THash128>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewWStringManaged(
  const aContext: TNewSynKeyValueContext; aValue: TTypeKind; var result);
var
  obj: pointer;
begin
  case aValue of
    tkLString:
      obj := TSynKeyValueSpecialized<WideString, RawByteString>.Create(aContext);
    tkWString:
      obj := TSynKeyValueSpecialized<WideString, WideString>.Create(aContext);
    tkUString:
      obj := TSynKeyValueSpecialized<WideString, UnicodeString>.Create(aContext);
    tkInterface:
      obj := TSynKeyValueSpecialized<WideString, IInterface>.Create(aContext);
    tkVariant:
      obj := TSynKeyValueSpecialized<WideString, Variant>.Create(aContext);
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;
{$endif SPECIALIZE_WSTRING}

class procedure Collections.NewUStringOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeValue: integer; var result);
var
  obj: pointer;
begin
  case aSizeValue of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<UnicodeString, Byte>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<UnicodeString, Word>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<UnicodeString, Integer>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<UnicodeString, Int64>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<UnicodeString, THash128>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewUStringManaged(
  const aContext: TNewSynKeyValueContext; aValue: TTypeKind; var result);
var
  obj: pointer;
begin
  case aValue of
    tkLString:
      obj := TSynKeyValueSpecialized<UnicodeString, RawByteString>.Create(aContext);
    {$ifdef SPECIALIZE_WSTRING}
    tkWString:
      obj := TSynKeyValueSpecialized<UnicodeString, WideString>.Create(aContext);
    {$endif SPECIALIZE_WSTRING}
    tkUString:
      obj := TSynKeyValueSpecialized<UnicodeString, UnicodeString>.Create(aContext);
    tkInterface:
      obj := TSynKeyValueSpecialized<UnicodeString, IInterface>.Create(aContext);
    tkVariant:
      obj := TSynKeyValueSpecialized<UnicodeString, Variant>.Create(aContext);
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewInterfaceOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeValue: integer; var result);
var
  obj: pointer;
begin
  case aSizeValue of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<IInterface, Byte>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<IInterface, Word>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<IInterface, Integer>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<IInterface, Int64>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<IInterface, THash128>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewInterfaceManaged(
  const aContext: TNewSynKeyValueContext; aValue: TTypeKind; var result);
var
  obj: pointer;
begin
  case aValue of
    tkLString:
      obj := TSynKeyValueSpecialized<IInterface, RawByteString>.Create(aContext);
    {$ifdef SPECIALIZE_WSTRING}
    tkWString:
      obj := TSynKeyValueSpecialized<IInterface, WideString>.Create(aContext);
    {$endif SPECIALIZE_WSTRING}
    tkUString:
      obj := TSynKeyValueSpecialized<IInterface, UnicodeString>.Create(aContext);
    tkInterface:
      obj := TSynKeyValueSpecialized<IInterface, IInterface>.Create(aContext);
    tkVariant:
      obj := TSynKeyValueSpecialized<IInterface, Variant>.Create(aContext);
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewVariantOrdinal(
  const aContext: TNewSynKeyValueContext; aSizeValue: integer; var result);
var
  obj: pointer;
begin
  case aSizeValue of
    {$ifdef SPECIALIZE_SMALL}
    1:
      obj := TSynKeyValueSpecialized<Variant, Byte>.Create(aContext);
    2:
      obj := TSynKeyValueSpecialized<Variant, Word>.Create(aContext);
    {$endif SPECIALIZE_SMALL}
    4:
      obj := TSynKeyValueSpecialized<Variant, Integer>.Create(aContext);
    8:
      obj := TSynKeyValueSpecialized<Variant, Int64>.Create(aContext);
    {$ifdef SPECIALIZE_HASH}
    16:
      obj := TSynKeyValueSpecialized<Variant, THash128>.Create(aContext);
    {$endif SPECIALIZE_HASH}
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class procedure Collections.NewVariantManaged(
  const aContext: TNewSynKeyValueContext; aValue: TTypeKind; var result);
var
  obj: pointer;
begin
  case aValue of
    tkLString:
      obj := TSynKeyValueSpecialized<Variant, RawByteString>.Create(aContext);
    {$ifdef SPECIALIZE_WSTRING}
    tkWString:
      obj := TSynKeyValueSpecialized<Variant, WideString>.Create(aContext);
    {$endif SPECIALIZE_WSTRING}
    tkUString:
      obj := TSynKeyValueSpecialized<Variant, UnicodeString>.Create(aContext);
    tkInterface:
      obj := TSynKeyValueSpecialized<Variant, IInterface>.Create(aContext);
    tkVariant:
      obj := TSynKeyValueSpecialized<Variant, Variant>.Create(aContext);
  else
    obj := RaiseUseNewPlainKeyValue(aContext);
  end;
  IKeyValue<Int64, Int64>(result) := TSynKeyValueSpecialized<Int64, Int64>({%H-}obj);
end;

class function Collections.NewList<T>(aOptions: TListOptions;
  aDynArrayTypeInfo: PRttiInfo): IList<T>;
begin
  // IsManagedType() GetTypeKind() SizeOf() intrinsics to compile efficiently
  if IsManagedType(T) then
    case GetTypeKind(T) of
      tkLString:
        // reuse TSynListSpecialized<RawByteString> for all AnsiString
        NewLString(aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
      {$ifdef SPECIALIZE_WSTRING}
      tkWString:
        // reuse TSynListSpecialized<WideString> for all WideString
        NewWString(aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
      {$endif SPECIALIZE_WSTRING}
      tkUString:
        // reuse TSynListSpecialized<UnicodeString> for all UnicodeString
        NewUString(aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
      tkInterface:
        // reuse TSynListSpecialized<IInterface> for all interfaces
        NewInterface(aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
      tkVariant:
        // reuse TSynListSpecialized<Variant> for Variant
        NewVariant(aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
      // we can't reuse tkDynArray because its RTL is TypeInfo-specific
    else
      // even if NewPlainList<T>() is not called nor used, it would be generated
      // with its full TSynListSpecialized<T> for no benefit but exe bloating
      // -> explicit abort at runtime to let the user fix the code ASAP
      RaiseUseNewPlainList(TypeInfo(T));
    end
  else
    if GetTypeKind(T) = tkFloat then
      // reuse TSynListSpecialized<> for floats (double or single only)
      NewFloat(aOptions, aDynArrayTypeInfo, TypeInfo(T), result)
    else
      // reuse TSynListSpecialized<integers> for ordinals (including TObject)
      NewOrdinal(SizeOf(T), aOptions, aDynArrayTypeInfo, TypeInfo(T), result);
end;

{$else}

class function Collections.NewList<T>(aOptions: TListOptions;
  aDynArrayTypeInfo: PRttiInfo): IList<T>;
begin
  // oldest Delphi will generate bloated code for each specific type
  result := TSynListSpecialized<T>.Create(aOptions, ptNone, aDynArrayTypeInfo);
end;

{$endif SPECIALIZE_ENABLED}

class function Collections.NewPlainList<T>(aOptions: TListOptions;
  aDynArrayTypeInfo: PRttiInfo; aSortAs: TRttiParserType): IList<T>;
begin
  result := TSynListSpecialized<T>.Create(aOptions, aSortAs, aDynArrayTypeInfo);
end;

class function Collections.NewKeyValue<TKey, TValue>(
  aOptions: TSynKeyValueOptions; aKeyDynArrayTypeInfo: PRttiInfo;
  aValueDynArrayTypeInfo: PRttiInfo; aTimeoutSeconds: cardinal;
  aCompressAlgo: TAlgoCompress; aHasher: THasher): IKeyValue<TKey, TValue>;
var
  ctx: TNewSynKeyValueContext;
begin
  ctx.Options := aOptions;
  ctx.KeyArrayTypeInfo := aKeyDynArrayTypeInfo;
  ctx.KeyItemTypeInfo := TypeInfo(TKey);
  ctx.ValueArrayTypeInfo := aValueDynArrayTypeInfo;
  ctx.ValueItemTypeInfo := TypeInfo(TValue);
  ctx.Timeout := aTimeOutSeconds;
  ctx.Compress := aCompressAlgo;
  ctx.Hasher := aHasher;
  {$ifdef SPECIALIZE_ENABLED}
  // IsManagedType() GetTypeKind() SizeOf() intrinsics to compile efficiently
  if IsManagedType(TKey) then
    case GetTypeKind(TKey) of
      tkLString:
        if IsManagedType(TValue) then
          NewLStringManaged(ctx, GetTypeKind(TValue), result)
        else
          NewLStringOrdinal(ctx, SizeOf(TValue), result);
      {$ifdef SPECIALIZE_WSTRING}
      tkWString:
        if IsManagedType(TValue) then
          NewWStringManaged(ctx, GetTypeKind(TValue), result)
        else
          NewWStringOrdinal(ctx, SizeOf(TValue), result);
      {$endif SPECIALIZE_WSTRING}
      tkUString:
        if IsManagedType(TValue) then
          NewUStringManaged(ctx, GetTypeKind(TValue), result)
        else
          NewUStringOrdinal(ctx, SizeOf(TValue), result);
      tkInterface:
        if IsManagedType(TValue) then
          NewInterfaceManaged(ctx, GetTypeKind(TValue), result)
        else
          NewInterfaceOrdinal(ctx, SizeOf(TValue), result);
      tkVariant:
        if IsManagedType(TValue) then
          NewVariantManaged(ctx, GetTypeKind(TValue), result)
        else
          NewVariantOrdinal(ctx, SizeOf(TValue), result);
    else
      RaiseUseNewPlainKeyValue(ctx);
    end
  else
    if IsManagedType(TValue) then
      case GetTypeKind(TValue) of
        tkLString:
          NewOrdinalLString(ctx, SizeOf(TKey), result);
        {$ifdef SPECIALIZE_WSTRING}
        tkWString:
          NewOrdinalWString(ctx, SizeOf(TKey), result);
        {$endif SPECIALIZE_WSTRING}
        tkUString:
          NewOrdinalUString(ctx, SizeOf(TKey), result);
        tkInterface:
          NewOrdinalInterface(ctx, SizeOf(TKey), result);
        tkVariant:
          NewOrdinalVariant(ctx, SizeOf(TKey), result);
      else
        RaiseUseNewPlainKeyValue(ctx);
      end
    else
      NewOrdinalOrdinal(ctx, SizeOf(TKey), SizeOf(TValue), result);
  {$else}
  // oldest Delphi will generate bloated code for each specific type
  result := TSynKeyValueSpecialized<TKey, TValue>.Create(ctx);
  {$endif SPECIALIZE_ENABLED}
end;

class function Collections.NewPlainKeyValue<TKey, TValue>(
  aOptions: TSynKeyValueOptions; aKeyDynArrayTypeInfo: PRttiInfo;
  aValueDynArrayTypeInfo: PRttiInfo; aTimeoutSeconds: cardinal;
  aCompressAlgo: TAlgoCompress; aHasher: THasher): IKeyValue<TKey, TValue>;
var
  ctx: TNewSynKeyValueContext;
begin
  ctx.Options := aOptions;
  ctx.KeyArrayTypeInfo := aKeyDynArrayTypeInfo;
  ctx.KeyItemTypeInfo := TypeInfo(TKey);
  ctx.ValueArrayTypeInfo := aValueDynArrayTypeInfo;
  ctx.ValueItemTypeInfo := TypeInfo(TValue);
  ctx.Timeout := aTimeOutSeconds;
  ctx.Compress := aCompressAlgo;
  ctx.Hasher := aHasher;
  result := TSynKeyValueSpecialized<TKey, TValue>.Create(ctx);
end;

{$else}

implementation

{$endif HASGENERICS} // do-nothing unit on oldest compilers



end.
